package virtualinit.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 简单常用工具,定义为不需要依赖额外jar包(或者常用的如spring apache common),如果需要依赖额外jar包,应该放在ServiceUtil中<br>
 * 在这个类中,你应该导入的都是jdk自带的类
 *
 * @author HuangHuai on 2019-06-27 11:30
 **/
@SuppressWarnings({"unused", "unchecked"})
public class CommonUtil implements Serializable {

    private static final long serialVersionUID = 7816069471546717762L;
    private static Log log = Log.instance;

    /**
     * e.g:
     * ""    return   true  <br>
     * " "    return   true  <br>
     * " CostDump"    return   false  <br>
     * "null"    return   true  考虑到前端如果为null,有时候他并不用空表示,而是null字符串<br>
     * <p>
     * 对于集合, Collection.isEmpty <br>
     * 如果判断null这里不提供方法，使用  if(obj ==null ) 更加方便
     */
    public static boolean isEmpty(Object o) {
        boolean flag = false;
        if (o == null) {
            flag = true;
        } else if (o instanceof CharSequence) {
            flag = o.toString().trim().length() == 0;
        } else if (o instanceof Collection) {
            flag = ((Collection<?>) o).isEmpty();
        } else if (o instanceof Map) {
            flag = ((Map<?, ?>) o).isEmpty();
        } else if (o.getClass().isArray()) {
            if (Array.getLength(o) == 0) {
                flag = true;
            }
            Object[] arr = (Object[]) o;
            if (arr.length == 1 && arr[0] == null) {
                flag = true;
            }
        } else if (o instanceof Boolean) {
            return (Boolean) o;
        }
        return flag;
    }

    public static boolean isNotEmpty(Object o) {
        return !isEmpty(o);
    }

    public static boolean isMapEmpty(Map<?, ?> m) {
        return m == null || m.isEmpty();
    }

    public static boolean isMapNotEmpty(Map<?, ?> m) {
        return !isMapEmpty(m);
    }

    public static boolean isCollectionEmpty(Collection<?> m) {
        return m == null || m.isEmpty();
    }

    public static boolean isCollectionNotEmpty(Collection<?> m) {
        return !isCollectionEmpty(m);
    }

    /**
     * 只要有一个为null 返回 true <br>
     * 注意，他的反义词是   所有都不为null  而不是 oneIsNotNull
     */
    public static boolean oneIsNull(Object... params) {
        if (params == null) {
            return true;
        }
        for (Object param : params) {
            if (param == null) {
                return true; //有一个为null 断言成功
            }
        }
        return false;
    }

    public static boolean allIsNull(Object... params) {
        if (params == null) {
            return true;
        }
        for (Object param : params) {
            if (param != null) {
                return false;
            }
        }
        return true;
    }

    public static boolean allIsNotNull(Object... params) {
        return !oneIsNull(params);
    }


    public static boolean oneIsEmpty(Object... params) {
        if (params == null) {
            return true;
        }
        for (Object param : params) {
            if (isEmpty(param)) {
                return true;
            }
        }
        return false;
    }

    public static boolean allIsEmpty(Object... params) {
        if (params == null) {
            return true;
        }
        for (Object param : params) {
            if (!isEmpty(param)) {
                return false;
            }
        }
        return true;
    }

    public static boolean allIsNotEmpty(Object... params) {
        return !oneIsEmpty(params);
    }

    /**
     * 只要有一个为null 返回 true <br>
     * 注意，他的反义词是   所有都不为null  而不是 oneIsNotNull
     */
    public static boolean oneIsNull(Object param1, Object param2) {
        return param1 == null || param2 == null;
    }

    public static boolean oneIsEmpty(Object param1, Object param2) {
        return isEmpty(param1) || isEmpty(param2);
    }

    public static boolean oneIsNull(Object param1, Object param2, Object param3) {
        return param1 == null || param2 == null || param3 == null;
    }

    public static boolean oneIsEmpty(Object param1, Object param2, Object param3) {
        return isEmpty(param1) || isEmpty(param2) || isEmpty(param3);
    }

    public static boolean oneIsNull(Object param1, Object param2, Object param3, Object param4) {
        return param1 == null || param2 == null || param3 == null || param4 == null;
    }

    public static boolean oneIsEmpty(Object param1, Object param2, Object param3, Object param4) {
        return isEmpty(param1) || isEmpty(param2) || isEmpty(param3) || isEmpty(param4);
    }

    /**
     * parent=http://xxxx/ <br>
     * sub = /abc/def <br>
     * result:http://xxxx/abc/def <br>
     */
    public static String appendUrl(String parent, String sub) {
        int parentLastIdx = parent.lastIndexOf('/');
        int subFirstIdx = sub.indexOf('/');
        if (parentLastIdx == parent.length() - 1) {
            if (subFirstIdx == 0) {
                // sub 以 /开头 去掉一个
                sub = sub.substring(1);
            }
        } else {
            if (subFirstIdx != 0) {
                // sub 也没有 /开头
                parent = parent + "/";
            }
        }
        return parent + sub;
    }


    public static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (Exception e) {
        }
    }

    /**
     *
     */
    public static String mkString(Object... arr) {
        return mkString("", ",", "", arr);
    }

    /**
     * <blockquote><pre>
     *     mkString("[", ",", "]","张三","李雷","韩梅梅") ==> [张三,李雷,韩梅梅]
     * </pre></blockquote>
     */
    public static String mkString(String pre, String middle, String suffix, Object... arr) {
        StringBuilder sb = new StringBuilder(100);
        sb.append(pre);
        if (arr != null) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    sb.append(middle);
                }
                sb.append(arr[i]);
            }
        }
        sb.append(suffix);
        return sb.toString();
    }


    /**
     * 主要是为了得到当前方法名和类名相关信息，以及行号
     */
    public static StackTraceElement getClassInfo() {
        return new Throwable().getStackTrace()[1];
    }

    /**
     * 本方法是第0层,StackTraceElement[]是倒着来的，main方法属于最底层，方法调用的终点属于第0层
     */
    public static StackTraceElement getClassInfo(int depth) {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        if (depth <= 0) {
            depth = 0;
        } else if (depth >= stackTrace.length) {
            depth = stackTrace.length - 1;
        }
        return stackTrace[depth];
    }

    public static String getClassAndMethod() {
        StackTraceElement classInfo = getClassInfo(2);
        return classInfo.getClassName() + "." + classInfo.getMethodName();
    }

    /**
     * 本方法属于depth=1<br>
     * 返回诸如: at ....com.hh.util.CommonUtil.main(CommonUtil.java:283)  的字符串<br>
     * <blockquote><pre>
     * 		内部调用：getLineInfo() 应该传入参数是0, 因为,getLineInfo方法本身所处的是1,其他方法调用,当然是0了
     * 	外部调用....com.hh.util.CommonUtil#getLineInfo(0) 也是传入参数0,然后返回的是调用类的行号信息
     * </pre></blockquote>
     *
     * @param depth 获取哪个类的StackTraceElement
     */
    public static String getLineInfo(int depth) {
        StackTraceElement classInfo = getClassInfo(depth);
        return classInfo.getClassName() + "." + classInfo.getMethodName()
                + "(" + classInfo.getFileName() + ":" + classInfo.getLineNumber() + ")";
    }

    //调用者是0 ，本方法是1，想要上一层 填-1
    public static String getLineInfo2(int depth, boolean simpleClassName) {
        //0 下层  2   2-2
        //1 本层  1   1-2
        //2上层   0   0-2
        //3+ 上...层  -1-2
        StackTraceElement classInfo = getClassInfo(Math.abs(depth - 2));
        String className = classInfo.getClassName();
        className = simpleClassName ? className.substring(className.lastIndexOf(".") + 1)
                : className;
        return className + "." + classInfo.getMethodName()
                + "(" + classInfo.getFileName() + ":" + classInfo.getLineNumber() + ")";
    }


    // --------------------------- io -----------------------------------------

    /**
     * 关流
     */
    public static void closeQuietly(AutoCloseable... closeables) {
        if (closeables != null && closeables.length > 0) {
            for (AutoCloseable closeable : closeables) {
                if (closeable != null) {
                    try {
                        closeable.close();
                    } catch (Exception e) {
                        // ignore
                    }
                }
            }
        }
    }

    public static void closeQuietly(boolean needLineInfo, AutoCloseable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
                if (needLineInfo) {
                    log.info("Closed {} at {}",
                            closeable.getClass().getName(), getLineInfo(3));//本方法属于第二层
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }

    //-----------------------------------------

    /**
     * 计算耗时 格式： Takes time: xxx s xxx ms;
     */
    public static String countTime(long startTime) {
        long countTime = System.currentTimeMillis() - startTime;
        return formatBraces("Takes time: {}s {}ms;", countTime / 1000, countTime % 1000);
    }

    static final Pattern sPattern = Pattern.compile("\\$\\{\\}|\\$|\\{\\}|%|\\?");

    /**
     * 模拟scala的字符串插值 s"name is $name" <br>
     * java编译器不支持所以只能 format("name is $",name) <br>
     * e.g: <br>
     * <blockquote><pre>
     * 			format("My name is $","jack");
     * 			format("My name is %","jack");
     * 			format("My name is ?","jack");
     * 			format("My name is {}","jack");
     * 			format("My name is ${}","jack");
     *
     * 			return "My name is jack"; <br>
     *         </blockquote></pre>
     * <p>
     * 占位符可以是: $ % {} ${} ? 最好只有一种并且不支持{name}这种<br>
     * 如果要增加占位符种类请修改{@link #sPattern} <br>
     */
    public static String format(String s, Object... args) {
        if (isEmpty(args)) {
            return s;
        }
        Matcher m = sPattern.matcher(s);
        StringBuilder sb = new StringBuilder();
        int left = 0;
        int argIndex = 0;
        while (m.find()) {
            if (argIndex >= args.length) {
                break;
            }
            sb.append(s, left, m.start());
            sb.append(args[argIndex++]); // append arg
            left = m.end();
        }
        if (left != s.length()) {
            sb.append(s, left, s.length());
        }
        return sb.toString();
    }


    /**
     * 目的是如果我这样写   ? ? ?  不知道这三个问号代表什么，因为上下文不存在没法推断 <br>
     * 但是我这么写   ${field} ${operation} ${value}  意思就明确很多： field=value
     * <blockquote><pre>
     *     format$("select * from ${table} as t where t.${field} = 11","user","age")
     *     ==>  select * from user as t where t.age=11
     * </pre></blockquote>
     *
     * <br>
     * format$ 意味着参数占位符是 ${}
     */
    public static String format$(String messagePattern, Object... argArray) {
        return formatPlaceholder(messagePattern, "${", "}", argArray);
    }

    /**
     * formatBraces("select * from {} where {} = {}" , "user", "age", 18);
     */
    public static String formatBraces(String messagePattern, Object... argArray) {
        return formatPlaceholder(messagePattern, "{", "}", argArray);
    }


    /**
     * formatBraces("select * from ? where ? = ?" , "user", "age", 18);
     */
    public static String formatQ(String messagePattern, Object... argArray) {
        return formatPlaceholder(messagePattern, "?", "?", argArray);
    }

    /**
     * <blockquote>
     * formatPlaceholder("select * from ? where ? ? ?", "?", "?", "user", "age", "=", "18");
     * ==> select * from user where age = 18
     * </blockquote>
     * <blockquote><pre>
     *     formatPlaceholder("select * from ${table} where ${field} ${operation} ${value}",
     *     "${", "}", "user", "age", "=", "18") ;
     *     ==> select * from user where age = 18
     * </pre></blockquote>
     *
     * @param messagePattern 带占位符的string字符串
     * @param preDelim       可以是是 ${ , abc, def, #, $之类的单个多个字符
     * @param endDelim       同上
     * @param argArray       参数列表
     */
    public static String formatPlaceholder(String messagePattern, String preDelim, String endDelim, Object... argArray) {
        if (argArray == null || argArray.length == 0) {
            return messagePattern;
        }
        int preDelimLen = preDelim.length();
        int endDelimLen = endDelim.length();
        boolean preEqualEnd = false;
        if (preDelim.equals(endDelim)) { //如果是相同的  ? & $ $$ 等pre==sufix
            preEqualEnd = true;
        }
        int i = 0; // 第几个{}占位符的开始索引
        int j; // 占位符{}的索引位置
        StringBuilder sbuf = new StringBuilder(50);
        for (int l = 0; l < argArray.length; l++) {
            j = messagePattern.indexOf(preDelim, i);
            if (j == -1) { //如果没有占位符，直接返回
                if (l == 0) {
                    return messagePattern;
                }
                break;
            } else {
                // indexOf函数包含fromIndex
                int endIdx;
                if (preEqualEnd) {
                    endIdx = j;
                } else {
                    endIdx = messagePattern.indexOf(endDelim, j + preDelimLen);
                }

                if (endIdx == -1) {
                    //说明只有  ${ 后面一半没有了，不把他当作参数占位
                    break;
                }

                int middlePreDelimIdx = messagePattern.indexOf(preDelim, j + preDelimLen);
                if (middlePreDelimIdx < endIdx && middlePreDelimIdx > j) {
                    j = middlePreDelimIdx;
                    //${ ${} 避免  跳过那个不全的占位符
                }

                sbuf.append(messagePattern, i, j); //append 非参数部分. 不包含j号索引
                sbuf.append(argArray[l]); //append参数部分
                i = endIdx + endDelimLen; //下一个非占位符索引位置, 是}号索引+endDelimLen
            }
        }

        //最后 没有发现占位需要把尾部append进去
        if (i != messagePattern.length()) {
            sbuf.append(messagePattern, i, messagePattern.length());
        }
        return sbuf.toString();
    }


    /**
     * is string matchRegex regex ?
     */
    public static boolean matchRegex(String s, String regex) {
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(s);
        return m.find();
    }


    /**
     * 在数组中，是否包含了指定字符串？对大小写敏感
     */
    public static boolean contains(String[] arr, String s) {
        return contains(Arrays.asList(arr), s, false);
    }


    public static boolean contains(List<String> list, String s) {
        return contains(list, s, false);
    }

    private static boolean contains(List<String> list, String value, boolean ignoreCase) {
        if (isCollectionEmpty(list)) {
            return false;
        } else {
            for (String s1 : list) {
                if (s1 != null) {
                    return s1.equals(value) || (ignoreCase && s1.equalsIgnoreCase(value));
                }
            }
        }
        return false;
    }

    public static boolean containsIgnoreCase(String[] arr, String s) {
        return contains(Arrays.asList(arr), s, true);
    }

    public static boolean containsIgnoreCase(List<String> list, String s) {
        return contains(list, s, true);

    }

    //	-------------------- 以下方法快速创建一个集合, 省去了new 操作 以及add put操作 -------------------------------------------

    public static <T> List<T> wrapList(T... values) {
        List<T> list = new ArrayList<>();
        if (isEmpty(values)) {
            return list;
        }

        for (T value : values) {
            list.add(value);
        }
        return list;
    }

    public static <T> Set<T> wrapSet(T... values) {
        Set<T> set = new HashSet<>();

        if (isEmpty(values)) {
            return set;
        }
        for (T value : values) {
            set.add(value);
        }
        return set;
    }

    public static <K, V> Map<K, V> wrapMap(K key, V value) {
        Map<K, V> map = new HashMap<>(3);

        if (isEmpty(key)) {
            return map;
        }

        map.put(key, value);
        return map;
    }

    public static <K, V> Map<K, V> wrapMaps(Object... kvs) {
        if (kvs == null || kvs.length == 0) return new HashMap<>(0);
        int len = kvs.length;
        Map<Object, Object> map = new HashMap<>(len * 2);
        for (int i = 0; i < len; i++) {
            if (i % 2 == 0) {
                if (i + 1 < len) {
                    map.put(kvs[i], kvs[i + 1]);
                }
            }
        }
        return (Map<K, V>) map;
    }

    public static <K, V> Map<K, V> wrapMap(K key, V value, int capacity) {
        Map<K, V> map = new HashMap<>(capacity);

        if (isEmpty(key)) {
            return map;
        }

        map.put(key, value);
        return map;
    }

    /**
     * 将目录进行拼接
     * abc/ ,def ==>  abc/def
     */
    public static String appendDir(String firstDir, String... subDirs) {
        if (CommonUtil.isEmpty(subDirs)) {
            return firstDir;
        }

        String replacement = "/";
        String fileSep = File.separator;
        if (firstDir.contains("/")) {
            fileSep = "/";
        } else {
            replacement = "\\\\";
        }

        firstDir = firstDir.replaceAll("[/|\\\\]+", replacement);
        StringBuilder sb = new StringBuilder(firstDir);
        String last = firstDir;
        for (String subDir : subDirs) {
            if (isEmpty(subDir)) {
                continue;
            }
            subDir = subDir.replaceAll("[/|\\\\]+", replacement);
            // 以 /\结尾
            if (last.lastIndexOf(fileSep) == firstDir.length() - 1) {
                if (subDir.indexOf(fileSep) == 0) {
                    // 以 /\开头
                    subDir = subDir.substring(1);
                }
            } else {
                if (subDir.indexOf(fileSep) != 0) {
                    // 不以 /\开头
                    subDir = fileSep + subDir;
                }
            }
            sb.append(subDir);
            last = subDir;
        }
        return sb.toString();
    }

    // ------------------- assert 断言空 抛出异常 -------------------------

    /**
     * if OneIsEmpty throw Exception
     */
    public static void assertOneIsEmpty(String msg, Object... param) {
        if (oneIsEmpty(param)) {
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * if OneIsEmpty throw Exception
     */
    public static void assertIsEmpty(String msg, Object param) {
        if (isEmpty(param)) {
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * if OneIsEmpty throw Exception
     */
    public static void assertIsNull(String msg, Object param) {
        if (param == null) {
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * if AllIsEmpty throw Exception
     */
    public static void assertAllIsEmpty(String msg, Object... param) {
        if (allIsEmpty(param)) {
            throw new IllegalArgumentException(msg);
        }
    }


    public static boolean containsFromMap(Map<?, ?> map, String path) {
        if (path == null || map == null || map.isEmpty()) {
            return false;
        }
        String[] split = path.split("\\.");
        Object data = map;
        for (int i = 0; i < split.length; i++) {
            if (data instanceof Map && ((Map) data).containsKey(split[i])) {
                data = ((Map) data).get(split[i]);
            } else {
                return false;
            }
        }
        return true;
    }

    public static Object searchFromMap(Map<?, ?> map, String path) {
        if (path == null || map == null || map.isEmpty()) {
            return false;
        }
        String[] split = path.split("\\.");
        Object data = map;
        for (int i = 0; i < split.length; i++) {
            if (data instanceof Map && ((Map<?, ?>) data).containsKey(split[i])) {
                data = ((Map<?, ?>) data).get(split[i]);
            } else {
                return null;
            }
        }
        return data;
    }

    public static Contains containsAndSearchFromMap(Map<?, ?> map, String path) {
        if (path == null || map == null || map.isEmpty()) {
            return Contains.noContain();
        }
        String[] split = path.split("\\.");
        Object d = map;
        for (int i = 0; i < split.length; i++) {
            if (d instanceof Map && ((Map<?, ?>) d).containsKey(split[i])) {
                d = ((Map<?, ?>) d).get(split[i]);
            } else {
                return Contains.noContain();
            }
        }
        return new Contains(true, d);
    }

    public static Contains containsFromMap2(Map<?, ?> m, String path) {
        String[] split = split(path, ".");
        Object data = m;
        for (int i = 0; i < split.length; i++) {
            if (data instanceof Map && ((Map<?, ?>) data).containsKey(split[i])) {
                data = ((Map<?, ?>) data).get(split[i]);
            } else {
                return Contains.noContain();
            }
        }
        return new Contains(true, data);
    }

    /**
     * 将 str 按照 Separator 切割成array
     */
    public static String[] split(String str, String separator) {
        int before = 0;
        int end = 0;

        int c = 0;
        String[] split = new String[3];
        if (str != null && separator != null && separator.length() > 0) {
            if (str.indexOf(separator, before) != -1) {
                while ((end = str.indexOf(separator, before)) != -1) {
                    split[c++] = str.substring(before, end);
                    before = end + 1;
                }
                split[c] = str.substring(before);
                return split;
            } else {
                return new String[]{str};
            }
        }
        return new String[0];
    }


    public static boolean eq(Number num, Number val) {
        if (num == null || val == null) {
            return false;
        }
        return num.equals(val);
    }

    public static String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }


    public static void illegalArgumentException(String msg, Object... args) throws IllegalArgumentException {
        throw new IllegalArgumentException(formatBraces(msg, args));
    }

    public static String exceptionToString(Throwable e) {
        if (e == null)
            return "";
        Bout o = new Bout(5000);
        o.write('\n');
        e.printStackTrace(new PrintStream(o));
        return o.toString();
    }

    public static class Contains {
        private boolean contain;
        private Object value;

        public Contains(boolean contain) {
            this.contain = contain;
        }

        public Contains(boolean contain, Object value) {
            this.contain = contain;
            this.value = value;
        }

        @Override
        public String toString() {
            return "{contains=" + contain + ", value=" + value + '}';
        }

        public static Contains noContain() {
            return new Contains(false);
        }
    }


    private static class Bout extends ByteArrayOutputStream {
        public Bout(int size) {
            super(size);
        }

        @Override
        public byte[] toByteArray() {
            return this.buf;
        }

        public String toString() {
            return new String(this.buf, 0, count, StandardCharsets.UTF_8);
        }

        @Override
        public int size() {
            return count;
        }
    }
}
